/**
 * Copyright (c) 2023 murenchao
 * taomu is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *       http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */
package cool.taomu.box.guice

import com.google.inject.AbstractModule
import com.google.inject.Binder
import com.google.inject.CreationException
import com.google.inject.Module
import cool.taomu.box.asm.CreateClass
import cool.taomu.box.asm.CreateMethod
import cool.taomu.box.asm.entity.ClassEntity
import cool.taomu.box.asm.entity.MethodEntity
import cool.taomu.box.guice.ann.Binders
import cool.taomu.box.guice.ann.BindersImpl
import cool.taomu.box.guice.ann.Box
import cool.taomu.box.guice.entity.BindersEntity
import cool.taomu.box.guice.entity.InterceptorEntity
import cool.taomu.box.guice.util.TaomuGuiceUitl
import java.util.ArrayList
import java.util.HashMap
import java.util.LinkedList
import java.util.List
import java.util.UUID
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.slf4j.LoggerFactory

class TaomuGuiceModule {
	val static LOG = LoggerFactory.getLogger(TaomuGuiceModule)
	val static boolean isDebug = true;
	val static ccloader = new HashMap<String, ClassLoader>()

	def static loadZlass(String name) {
		LOG.info("load Class:{}", name);
		if (ccloader.containsKey(name)) {
			return ccloader.get(name).loadClass(name);
		} else {
			return Thread.currentThread.contextClassLoader.loadClass(name);
		}
	}

	def build(List<InterceptorEntity> ir) {
		return new AbstractModule() {
			override void configure() {
				val binder = binder();
				ir.filterNull.forEach [
					binder.bindInterceptor(it.classMatcher, it.methodMatcher, it.interceptors)
				]
			}
		}
	}

	def Module build(Class<?> ... zlasses) throws CreationException {
		val cacheBinders = new LinkedList<BindersEntity>();
		val cacheModule = new LinkedList<Class<? extends Module>>();
		val modules = new ArrayList<Class<? extends Module>>();
		zlasses.filterNull.forEach [ zlass, i |
			LOG.info("配置类：{}", zlass.name)
			if (zlass.interfaces.findFirst[it.equals(Module)] !== null || zlass.superclass.equals(AbstractModule)) {
				modules.add(zlass as Class<? extends Module>);
			}
			val box = zlass.getAnnotation(Box);
			val binders = zlass.getAnnotation(Binders);
			val binder = zlass.getAnnotation(cool.taomu.box.guice.ann.Binder);
			if (box !== null) {
				box.value.forEach [
					cacheBinders.add(TaomuGuiceUitl.toBinders(it));
				]
			} else if (binders !== null) {
				var bs = TaomuGuiceUitl.toBinders(binders);
				cacheBinders.add(bs);
			} else if (binder !== null) {
				var tgb = TaomuGuiceUitl.toBinders(new BindersImpl(#[binder]));
				cacheBinders.add(tgb);
			}
		]
		if (cacheBinders.size > 0) {
			cacheBinders.forEach [ it, i |
				// 构建module类
				cacheModule.add(buildModule(it, isDebug))
			]
		}
		if (cacheModule.size > 0) {
			// 创建一个根Module类
			var name = UUID.randomUUID.toString.replace("-", "");
			var moduleName = #["Taomu", name, "BaseModule"].join("");
			var classEntity = new ClassEntity("cool.taomu.box", moduleName);
			classEntity.superclass = Type.getType(AbstractModule).internalName;
			var cc = CreateClass.Class(classEntity, Opcodes.V1_8, isDebug);
			cc.constructor(AbstractModule.constructors);
			var methodEntity = new MethodEntity("void configure()");
			var binder = new MethodEntity("com/google/inject/Binder binder()", true);
			val cm = cc.method(methodEntity).This.invokeVirtual(binder).store("binder", Binder)
			// 组装多个module类
			modules.filterNull.forEach [
				LOG.info("安装Module类：{}", it.name)
				ginstall(cm, it);
			]
			// 组装多个module类
			cacheModule.forEach [
				LOG.info("安装Module类：{}", it.name)
				ginstall(cm, it);
			]
			// 结束创建的方法
			cm.returnValue.endMethod;
			// 结束创建的类
			cc.end;
			var key = #["cool.taomu.box", moduleName].join(".");
			ccloader.put(key, cc);
			// 实例创建的类 
			return (cc.loadClass(key) as Class<Module>).newInstance;
		}
		return null
	}

	private def CreateMethod ginstall(CreateMethod cm, Class<? extends Module> zlass) {
		cm.ldc(zlass.name);
		cm.invokeStatic(
			new MethodEntity(TaomuGuiceModule.name.replace(".", "/"), "java/lang/Class loadZlass(java/lang/String)",
				true));
		cm.invokeVirtual(new MethodEntity("java/lang/Class", "java/lang/Object newInstance()", true));
		cm.store(zlass.name, Object)
		cm.load("binder")
		cm.load(zlass.name)
		cm.checkCast(Module)
		cm.invokeInterface(new MethodEntity("com/google/inject/Binder", "void install(com/google/inject/Module)", true))
	}

	private def Class<? extends Module> buildModule(BindersEntity binders, boolean isDebug) {
		if (binders !== null) {
			var name = UUID.randomUUID.toString.replace("-", "");
			var moduleName = #["Taomu", name, "Module"].join("");
			var classEntity = new ClassEntity("cool.taomu.box", moduleName);
			classEntity.superclass = Type.getType(AbstractModule).internalName;
			var cc = CreateClass.Class(classEntity, Opcodes.V1_8, isDebug);
			cc.constructor(AbstractModule.constructors);
			var methodEntity = new MethodEntity("void configure()");
			var binder = new MethodEntity("com/google/inject/Binder binder()", true);
			val cm = cc.method(methodEntity).This.invokeVirtual(binder).store("binder", Binder)
			if (!binders.properties.nullOrEmpty) {
				cm.load("binder");
				cm.ldc(binders.properties);
				cm.invokeStatic(
					new MethodEntity("cool/taomu/util/PropertyUtils",
						"java/util/Properties load(java/lang/String)", true));
				cm.invokeStatic(
					new MethodEntity("com/google/inject/name/Names",
						"void bindProperties(com/google/inject/Binder,java/util/Properties)", true));
			}
			if (binders.installs !== null && binders.installs.size > 0) {
				binders.installs.forEach [
					cm.NEW(it);
					cm.dup();
					cm.invokeSpecial(new MethodEntity(it.name, "void <init>()"));
					cm.store(it.name, it);
					cm.load("binder")
					cm.load(it.name)
					cm.invokeInterface(
						new MethodEntity("com/google/inject/Binder", "void install(com/google/inject/Module)", true));
				]
			}
			BinderRule.rule(cm, binders.values,ccloader);
			cm.returnValue.endMethod;
			cc.end;
			var key = #["cool.taomu.box", moduleName].join(".");
			ccloader.put(key, cc);
			LOG.info("end");
			return cc.loadClass(key) as Class<Module>;
		}
	}
}
